
import { RenderData } from 'cc';
import { Enum } from 'cc';
import { _decorator, Component, Node, Material, Sprite, gfx, IAssembler, Color, __private, UI, SpriteFrame } from 'cc';
import { EDITOR } from 'cc/env';
const { ccclass, property, executeInEditMode } = _decorator;

const Attribute = gfx.Attribute;

const attributes: gfx.Attribute[] = [
    new Attribute('a_position', gfx.Format.RGB32F),
    new Attribute('a_texCoord', gfx.Format.RG32F),
    new Attribute('a_color', gfx.Format.RGBA32F),
    new Attribute('a_texture_idx', gfx.Format.R32F),
];

/**
 * @en Vertex format with the following layout
 * 1. Vector 3 position attribute (Float32)
 * 2. Vector 2 uv attribute (Float32)
 * 3. Vector 4 color attribute (Float32)
 * @zh 包含以下数据的顶点格式
 * 1. 三维位置属性（Float32）
 * 2. 二维贴图 UV 属性（Float32）
 * 3. RGBA 颜色属性（Float32）
 */
export const vfmtPosUvColorTex = [
    new Attribute('a_position', gfx.Format.RGB32F),
    new Attribute('a_texCoord', gfx.Format.RG32F),
    new Attribute('a_color', gfx.Format.RGBA32F),
    new Attribute('a_texture_idx', gfx.Format.R32F),
];

interface IRenderData {
    x: number;
    y: number;
    z: number;
    u: number;
    v: number;
    color: Color;
}

enum RenderDrawInfoType {
    COMP,
    MODEL,
    MIDDLEWARE,
    SUB_NODE,
}

const QUAD_INDICES = Uint16Array.from([0, 1, 2, 1, 3, 2]);

const simple: IAssembler = {
    createData (sprite: Sprite) {
        const renderData = sprite.requestRenderData(); // 渲染所需的数据对象
        renderData.dataLength = 4;  // 4 vertices 四个角的顶点
        renderData.resize(4, 6);    // 此处会分配一个chunk, 4顶点, 6索引
        renderData.vertexRow = 2;   // 2 floats per vertex
        renderData.vertexCol = 2;   // 2 floats per vertex
        renderData.chunk.setIndexBuffer(QUAD_INDICES); // 绘制一个矩形固定的6个索引012132

        // renderData.chunk.meshBuffer.vData = new Float32Array(4 * 10);

        return renderData;

        // renderData.vData = new Float32Array(4 * 10);
        // return renderData;
    },

    updateRenderData (sprite: MultiTexture2d) {
        const frame = sprite.spriteFrame;

        const renderData = sprite.renderData;
        if (renderData && frame) {
            if (renderData.vertDirty) {
                this.updateVertexData(sprite);
            }
            this.updateUVs(sprite);
            if (sprite.textureIdxDirty) {
                this.updateTextureIdx(sprite);
            }
        }
    },

    updateWorldVerts (sprite: Sprite, chunk: any) {
        const renderData = sprite.renderData!;
        const vData = chunk.vb;

        const dataList: IRenderData[] = renderData.data;
        const node = sprite.node;
        const m = node.worldMatrix;

        const stride = renderData.floatStride;
        let offset = 0;
        const length = dataList.length;
        for (let i = 0; i < length; i++) {
            const curData = dataList[i];
            const x = curData.x;
            const y = curData.y;
            let rhw = m.m03 * x + m.m07 * y + m.m15;
            rhw = rhw ? Math.abs(1 / rhw) : 1;

            offset = i * stride;
            vData[offset + 0] = (m.m00 * x + m.m04 * y + m.m12) * rhw;
            vData[offset + 1] = (m.m01 * x + m.m05 * y + m.m13) * rhw;
            vData[offset + 2] = (m.m02 * x + m.m06 * y + m.m14) * rhw;
        }
    },

    fillBuffers (sprite: Sprite, renderer: any) {
        if (sprite === null) {
            return;
        }

        const renderData = sprite.renderData!;
        const chunk = renderData.chunk;
        if (sprite.node.hasChangedFlags || renderData.vertDirty) {
            // const vb = chunk.vertexAccessor.getVertexBuffer(chunk.bufferId);
            this.updateWorldVerts(sprite, chunk);
            renderData.vertDirty = false;
        }

        // quick version
        const bid = chunk.bufferId;
        const vidOrigin = chunk.vertexOffset;
        const meshBuffer = chunk.meshBuffer;
        const ib = chunk.meshBuffer.iData;
        let indexOffset = meshBuffer.indexOffset;

        // rect count = vertex count - 1
        for (let curRow = 0; curRow < renderData.vertexRow - 1; curRow++) {
            for (let curCol = 0; curCol < renderData.vertexCol - 1; curCol++) {
                // vid is the index of the left bottom vertex in each rect.
                const vid = vidOrigin + curRow * renderData.vertexCol + curCol;

                // left bottom
                ib[indexOffset++] = vid;
                // right bottom
                ib[indexOffset++] = vid + 1;
                // left top
                ib[indexOffset++] = vid + renderData.vertexCol;

                // right bottom
                ib[indexOffset++] = vid + 1;
                // right top
                ib[indexOffset++] = vid + 1 + renderData.vertexCol;
                // left top
                ib[indexOffset++] = vid + renderData.vertexCol;

                // IndexOffset should add 6 when vertices of a rect are visited.
                meshBuffer.indexOffset += 6;
            }
        }
        // slow version
        // renderer.switchBufferAccessor().appendIndices(chunk);
    },

    updateVertexData (sprite: Sprite) {
        const renderData: RenderData | null = sprite.renderData;
        if (!renderData) {
            return;
        }

        const uiTrans = sprite.node._uiProps.uiTransformComp!;
        const dataList: IRenderData[] = renderData.data;
        const cw = uiTrans.width;
        const ch = uiTrans.height;
        const appX = uiTrans.anchorX * cw;
        const appY = uiTrans.anchorY * ch;
        let l = 0;
        let b = 0;
        let r = 0;
        let t = 0;
        if (sprite.trim) {
            l = -appX;
            b = -appY;
            r = cw - appX;
            t = ch - appY;
        } else {
            const frame = sprite.spriteFrame!;
            const originSize = frame.originalSize;
            const ow = originSize.width;
            const oh = originSize.height;
            const scaleX = cw / ow;
            const scaleY = ch / oh;
            const trimmedBorder = frame.trimmedBorder;
            l = trimmedBorder.x * scaleX - appX;
            b = trimmedBorder.z * scaleY - appY;
            r = cw + trimmedBorder.y * scaleX - appX;
            t = ch + trimmedBorder.w * scaleY - appY;
        }

        dataList[0].x = l;
        dataList[0].y = b;

        dataList[1].x = r;
        dataList[1].y = b;

        dataList[2].x = l;
        dataList[2].y = t;

        dataList[3].x = r;
        dataList[3].y = t;

        renderData.vertDirty = true;
    },

    updateUVs (sprite: Sprite) {
        if (!sprite.spriteFrame) return;
        const renderData = sprite.renderData!;
        const vData = renderData.chunk.vb;
        const uv = sprite.spriteFrame.uv;
        if (!uv) {
            console.error(`uv is missing : ${sprite.node.name}`, sprite)
            return;
        }
        vData[3] = uv[0];
        vData[4] = uv[1];
        vData[13] = uv[2];
        vData[14] = uv[3];
        vData[23] = uv[4];
        vData[24] = uv[5];
        vData[33] = uv[6];
        vData[34] = uv[7];
    },

    updateColor (sprite: Sprite) {
        const renderData = sprite.renderData!;
        const vData = renderData.chunk.vb;
        let colorOffset = 5;
        const color = sprite.color;
        const colorR = color.r / 255;
        const colorG = color.g / 255;
        const colorB = color.b / 255;
        const colorA = color.a / 255;
        for (let i = 0; i < 4; i++, colorOffset += renderData.floatStride) {
            vData[colorOffset] = colorR;
            vData[colorOffset + 1] = colorG;
            vData[colorOffset + 2] = colorB;
            vData[colorOffset + 3] = colorA;
        }
    },

    updateTextureIdx(sprite: MultiTexture2d) {
        const renderData = sprite.renderData!;
        if (renderData && renderData.chunk) {
            const vData = renderData.chunk.vb;
    
            vData[9] = sprite.textureIdx;
            vData[19] = sprite.textureIdx;
            vData[29] = sprite.textureIdx;
            vData[39] = sprite.textureIdx;
    
            sprite.textureIdxDirty = false;
        }
    }
};

enum TEXTURE_IDX {
    TEX_0 = 0,
    TEX_1,
    TEX_2,
    TEX_3,
    TEX_4,
    TEX_5,
    TEX_6,
    TEX_7,
}
 
@ccclass('MultiTexture2d')
@executeInEditMode
export class MultiTexture2d extends Sprite {
    @property({ type: Enum(TEXTURE_IDX) })
    private _textureIdx: TEXTURE_IDX = TEXTURE_IDX.TEX_0;
    @property({ type: Enum(TEXTURE_IDX), tooltip: '纹理索引从0开始' })
    set textureIdx(idx: TEXTURE_IDX) {
        this._textureIdx = idx;
        this.textureIdxDirty = true;
        simple.updateTextureIdx(this);
    }
    get textureIdx(): TEXTURE_IDX {
        return this._textureIdx;
    }
    textureIdxDirty: boolean = true;
    
    handle: number = -1;
    // @ts-ignore
    public requestRenderData (drawInfoType = RenderDrawInfoType.COMP) {
        const data = RenderData.add(attributes);
        // @ts-ignore
        data.initRenderDrawInfo(this, drawInfoType);
        this._renderData = data;
        return data;
    }

    start () {
        // @ts-ignore
        // 因为已经批量上传了纹理，所以就不需要引擎计算的texture哈希值，不然会打断dc
        this.spriteFrame._texture._textureHash = 9999;
    }

    protected _flushAssembler () {
        const assembler = simple;

        if (this._assembler !== assembler) {
            this.destroyRenderData();
            this._assembler = assembler;
        }

        if (!this._renderData) {
            if (this._assembler && this._assembler.createData) {
                this._renderData = this._assembler.createData(this);
                this._renderData!.material = this.getRenderMaterial(0);
                this.markForUpdateRenderData();
                if (this.spriteFrame) {
                    this._assembler.updateUVs(this);
                }
                this._updateColor();
            }
        }
    }
}
